namespace Microsoft.Robotics.Tracing
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Globalization;
    using System.Reflection;

    /// <summary>
    /// An implementation of trace writer that uses System.Diagnostics class for the output.
    /// Thus, configuring System tracers (i.e. from application manifest) defines how this class behaves.
    /// </summary>
    public class SystemDiagnosticsTraceWriter : ITraceWriter
    {
        /// <summary>
        /// Trace level to use if no trace level is found in app.config
        /// </summary>
        private const string DefaultTraceLevel = "Info";

        /// <summary>
        /// Externally supplied formatter
        /// </summary>
        private ITraceFormatter formatter;

        /// <summary>
        /// Dictionary of TraceContext IDs to TraceLevelIDs
        /// </summary>
        private Dictionary<int, TraceLevelIDs> traceLevelsCache;

        /// <summary>
        /// Initializes a new instance of the <see cref="SystemDiagnosticsTraceWriter" /> class.
        /// </summary>
        /// <param name="formatter">Externally supplied formatter</param>
        public SystemDiagnosticsTraceWriter(ITraceFormatter formatter)
        {
            this.formatter = formatter;
            this.traceLevelsCache = new Dictionary<int, TraceLevelIDs>();
        }

        /// <summary>
        /// Write to trace log
        /// </summary>
        /// <param name="dateTime">Date time of event</param>
        /// <param name="level">The level</param>
        /// <param name="context">Context of event</param>
        /// <param name="format">Text format</param>
        /// <param name="args">The arguments</param>
        public void WriteLine(DateTime dateTime, TraceContext level, TraceContext context, string format, params object[] args)
        {
            TraceLevelIDs traceLevel;

            if (!this.traceLevelsCache.TryGetValue(context.ID, out traceLevel))
            {
                // Parent context is the substring of TraceContext.Text up to the first '.'
                string[] contextSubstrings = context.Text.Split(".".ToCharArray(), 2);
                TraceSwitch traceSwitch = new TraceSwitch(contextSubstrings[0], string.Empty, DefaultTraceLevel);
                traceLevel = this.DiagnosticsTraceLevelToTraceLevelID(traceSwitch.Level);
                this.traceLevelsCache.Add(context.ID, traceLevel);
            }

            if (level.ID != (int)TraceLevelIDs.Off && (int)traceLevel >= (int)level.ID)
            {
                System.Diagnostics.Trace.WriteLine(this.formatter.Format(dateTime, level, context, format, args));
            }
        }

        /// <summary>
        /// Converts from System.Diagnostics.TraceLevel to corresponding Microsoft.Robotics.Tracing.TraceLevelIDs
        /// </summary>
        /// <param name="traceLevel">A System.Diagnostics.TraceLevel.</param>
        /// <returns>A Microsoft.Robotics.Tracing.TraceLevelIDs.</returns>
        private TraceLevelIDs DiagnosticsTraceLevelToTraceLevelID(TraceLevel traceLevel)
        {
            switch (traceLevel)
            {
                case TraceLevel.Off:
                    return TraceLevelIDs.Off;
                case TraceLevel.Error:
                    return TraceLevelIDs.Error;
                case TraceLevel.Warning:
                    return TraceLevelIDs.Warning;
                case TraceLevel.Info:
                    return TraceLevelIDs.Information;
                case TraceLevel.Verbose:
                    return TraceLevelIDs.Verbose;
                default:
                    throw new ArgumentException(string.Format("Unknown trace level specified {0}", this.traceLevelsCache));
            }
        }
    }
}
